#pragma once

#include <iostream>
#include <GL/glew.h>
#include <cmath>

namespace cagd
{
        class HCoordinate
	{
        public:
            float x, y, z, w;

            HCoordinate(): x(0.0), y(0.0), z(0.0), w(1.0) {}
            HCoordinate(float x, float y, float z = 0.0, float w = 1.0): x(x), y(y), z(z), w(w) {}

            GLfloat operator[](GLint rhs) const;
            GLfloat& operator[](GLint rhs);

            HCoordinate operator +(const HCoordinate &rhs) const
            {
                return HCoordinate(rhs.w * x + w * rhs.x,
                                   rhs.w * y + w * rhs.y,
                                   rhs.w * z + w * rhs.z,
                                   w * rhs.w);
            }

            HCoordinate& operator +=(const HCoordinate &rhs)
            {
                x = rhs.w * x + w * rhs.x;
                y = rhs.w * y + w * rhs.y;
                z = rhs.w * z + w * rhs.z;
                w = w * rhs.w;
                return *this;
            }

            HCoordinate operator -(const HCoordinate &rhs) const
            {
                return HCoordinate(rhs.w * x - w * rhs.x,
                                   rhs.w * y - w * rhs.y,
                                   rhs.w * z - w * rhs.z,
                                   w * rhs.w);
            }

            HCoordinate& operator -=(const HCoordinate &rhs)
            {
                x = rhs.w * x - w * rhs.x;
                y = rhs.w * y - w * rhs.y;
                z = rhs.w * z - w * rhs.z;
                w = w * rhs.w;
                return *this;
            }

            GLfloat operator *(const HCoordinate &rhs) const
            {
                return  rhs.w * x * w * rhs.x +
                        rhs.w * y * w * rhs.y +
                        rhs.w * z * w * rhs.z;
            }

            HCoordinate operator %(const HCoordinate &rhs) const
            {
                GLfloat x1 = x, y1 = y, z1 = z, x2 = rhs.x, y2 = rhs.y, z2 = rhs.z;
                x1 *= rhs.w;
                y1 *= rhs.w;
                z1 *= rhs.w;
                x2 *= w;
                y2 *= w;
                z2 *= w;
                return HCoordinate(y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2, w * rhs.w);
            }

            HCoordinate& operator %=(const HCoordinate &rhs)
            {
                GLfloat x1 = x, y1 = y, z1 = z, x2 = rhs.x, y2 = rhs.y, z2 = rhs.z;
                x1 *= rhs.w;
                y1 *= rhs.w;
                z1 *= rhs.w;
                x2 *= w;
                y2 *= w;
                z2 *= w;
                x = y1 * z2 - z1 * y2;
                y = z1 * x2 - x1 * z2;
                z = x1 * y2 - y1 * x2;
                w *= rhs.w;
                return *this;
            }

            HCoordinate& operator *=(GLfloat rhs)
            {
                    x *= rhs;
                    y *= rhs;
                    z *= rhs;
                    return *this;
            }

            HCoordinate operator *(GLfloat rhs) const
            {
                return HCoordinate(x * rhs, y * rhs, z * rhs, w);
            }

            HCoordinate& operator /=(GLfloat rhs)
            {
                    x /= rhs;
                    y /= rhs;
                    z /= rhs;
                    return *this;
            }

            HCoordinate operator /(GLfloat rhs) const
            {
                return HCoordinate(x / rhs, y / rhs, z / rhs, w);
            }

            GLfloat Magnitude() const
            {
                return sqrt((x * x + y * y + z * z) / (w * w));
            }

            HCoordinate& Normalize()
            {
                float length = sqrt((x * x + y * y + z * z) / (w * w));
                if (length && length != 1.0)
                {
                        x /= length;
                        y /= length;
                        z /= length;
                }
                return *this;
            }
	};

        inline HCoordinate operator *(GLfloat lhs, const HCoordinate &rhs)
	{
            return HCoordinate(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w);
        }

	// output
        inline std::ostream& operator << (std::ostream &lhs, const HCoordinate &hc)
	{
		return lhs << hc.x << " " << hc.y << " " << hc.z << " " << hc.w;
	}

	// input
        inline std::istream& operator >> (std::istream &lhs, HCoordinate &hc)
        {
                return lhs >> hc.x >> hc.y >> hc.z >> hc.w;
        }
}
